#include <boost/lexical_cast.hpp>

#include "scene/octree.h"
#include "core/logger.h"
#include "map/worldmesh.h"

enum ChildNode {
	CN_BOTTOM_FRONT_LEFT = 0,
	CN_BOTTOM_BACK_LEFT,
	CN_BOTTOM_BACK_RIGHT,
	CN_BOTTOM_FRONT_RIGHT,
	CN_TOP_FRONT_LEFT,
	CN_TOP_BACK_LEFT,
	CN_TOP_BACK_RIGHT,
	CN_TOP_FRONT_RIGHT,
};

namespace engine {
		
	namespace scene {
		using engine::map::WorldVertex;
		
		using boost::lexical_cast;
		
		Octree::Octree() {
			Logger::getLogger("octree")->setLevel(LL_DEBUG);
		}
		
		Octree::~Octree() {
			
		}
		
		shared_ptr<OctreeNode> Octree::getRootNode() {
			return m_RootNode;
		}
		
		slVec3 Octree::calcCentre(const AABB& bounds) {
			float x = bounds.getMin().m_X + ((bounds.getMax().m_X - bounds.getMin().m_X) * 0.5f); 
			float y = bounds.getMin().m_Y + ((bounds.getMax().m_Y - bounds.getMin().m_Y) * 0.5f);
			float z = bounds.getMin().m_Z + ((bounds.getMax().m_Z - bounds.getMin().m_Z) * 0.5f);
			
			Logger::getLogger("octree")->debug("World centre: ");
			Logger::getLogger("octree")->debug("X: " + lexical_cast<string> (x));
			Logger::getLogger("octree")->debug("Y: " + lexical_cast<string> (y));
			Logger::getLogger("octree")->debug("Z: " + lexical_cast<string> (z));
			
			return slVec3(x, y, z);
		}
		
		bool Octree::buildTree(WorldMesh& mesh) {
			getWorldBounds(mesh);
			calcCentre(m_WorldBounds);
			
			m_RootNode = shared_ptr<OctreeNode> (new OctreeNode());
			
			m_RootNode->buildTree(mesh.getVertexData(), mesh.getTriangles(),
									m_Bounds.getMaxWidth(),
									m_Centre);
			return true;
		}
		
		shared_ptr<OctreeNode> OctreeNode::findBestNode(const AABB& box) {
		}
		
		void Octree::getWorldBounds(WorldMesh& mesh) {
			Logger::getLogger("octree")->debug("Getting octree bounds");
			
			for (vector<WorldVertex>::const_iterator it = mesh.getVertexData().begin();
				it != mesh.getVertexData().end(); ++it) {
			
				if ((*it).m_Pos.m_X < m_WorldBounds.getMin().m_X) 
					m_WorldBounds.setMinX((*it).m_Pos.m_X);
					
				if ((*it).m_Pos.m_Y < m_WorldBounds.getMin().m_Y) 
					m_WorldBounds.setMinY((*it).m_Pos.m_Y);
					
				if ((*it).m_Pos.m_Z < m_WorldBounds.getMin().m_Z) 
					m_WorldBounds.setMinZ((*it).m_Pos.m_Z);
					
				if ((*it).m_Pos.m_X > m_WorldBounds.getMax().m_X) 
					m_WorldBounds.setMaxX((*it).m_Pos.m_X);
					
				if ((*it).m_Pos.m_Y > m_WorldBounds.getMax().m_Y) 
					m_WorldBounds.setMaxY((*it).m_Pos.m_Y);
					
				if ((*it).m_Pos.m_Z > m_WorldBounds.getMax().m_Z) 
					m_WorldBounds.setMaxZ((*it).m_Pos.m_Z);
			}
			
			Logger::getLogger("octree")->debug("Bounds are: ");
			Logger::getLogger("octree")->debug("- Min: (" + lexical_cast<string>(m_WorldBounds.getMin().m_X) + ", " 
														 + lexical_cast<string>(m_WorldBounds.getMin().m_Y) + ", " 
														 + lexical_cast<string>(m_WorldBounds.getMin().m_Z) + ")" );
			
			Logger::getLogger("octree")->debug("- Max: (" + lexical_cast<string>(m_WorldBounds.getMax().m_X) + ", " 
														 + lexical_cast<string>(m_WorldBounds.getMax().m_Y) + ", " 
														 + lexical_cast<string>(m_WorldBounds.getMax().m_Z) + ")" );
		}
		
		void OctreeNode::setCentre(const slVec3& centre) {
			m_Centre = centre;
		}
		
		void OctreeNode::createChildAABB(AABB& box, uint childPos, const float parentWidth, const slVec3& parentCentre) {
			switch(childPos) {
				case CN_BOTTOM_FRONT_LEFT:
					box = AABB(slVec3(	parentCentre.m_X - parentWidth/4, 
										parentCentre.m_Y - parentWidth/4,
										parentCentre.m_Z + parentWidth/4), parentWidth/2);
				break;
				case CN_BOTTOM_BACK_LEFT:
					box = AABB(slVec3(	parentCentre.m_X - parentWidth/4, 
										parentCentre.m_Y - parentWidth/4,
										parentCentre.m_Z - parentWidth/4), parentWidth/2);
				break;
				case CN_BOTTOM_BACK_RIGHT:
					box = AABB(slVec3(	parentCentre.m_X + parentWidth/4, 
										parentCentre.m_Y - parentWidth/4,
										parentCentre.m_Z - parentWidth/4), parentWidth/2);
				break;
				case CN_BOTTOM_FRONT_RIGHT:
					box = AABB(slVec3(	parentCentre.m_X + parentWidth/4, 
										parentCentre.m_Y - parentWidth/4,
										parentCentre.m_Z + parentWidth/4), parentWidth/2);
				break;
				
				case CN_TOP_FRONT_LEFT:
					box = AABB(slVec3(	parentCentre.m_X - parentWidth/4, 
										parentCentre.m_Y + parentWidth/4,
										parentCentre.m_Z + parentWidth/4), parentWidth/2);
				break;
				case CN_TOP_BACK_LEFT:
					box = AABB(slVec3(	parentCentre.m_X - parentWidth/4, 
										parentCentre.m_Y + parentWidth/4,
										parentCentre.m_Z - parentWidth/4), parentWidth/2);
				break;
				case CN_TOP_BACK_RIGHT:
					box = AABB(slVec3(	parentCentre.m_X + parentWidth/4, 
										parentCentre.m_Y + parentWidth/4,
										parentCentre.m_Z - parentWidth/4), parentWidth/2);
				break;
				case CN_TOP_FRONT_RIGHT:
					box = AABB(slVec3(	parentCentre.m_X + parentWidth/4, 
										parentCentre.m_Y + parentWidth/4,
										parentCentre.m_Z + parentWidth/4), parentWidth/2);
				break;
			}
		}
		
		void OctreeNode::addTriangleToNode(const WorldTriangle& tri, const vector<WorldVertex>& vertices) {
			//If we have the 
			shared_ptr<MiniNode> node;
			
			//Find the material node if there is one
			node = m_RootChild->findNodeByName(tri.m_TextureName);
			
			//If not create a material node for this material
			if (!node) {
				node = shared_ptr<MiniMaterialNode>(new MiniMaterialNode());
				shared_ptr<Resource> mat = getMaterialManager->getResource(tri.m_TextureName);
				if (!mat) {
					Logger::getLogger("scenemanager")->warning("Could not find material: " + tri.m_TextureName);
					return;
				}
				
				//Set the material
				node->setMaterial(mat);
				
				//Attach to the root node
				m_RootChild->attachNode(node);
			}
			

			shared_ptr<MiniGeomNode> newNode(new MiniGeomNode());
			
			newNode->m_Vertices.push_back(vertices[tri.m_Index[0]]);
			newNode->m_Vertices.push_back(vertices[tri.m_Index[1]]);
			newNode->m_Vertices.push_back(vertices[tri.m_Index[2]]);
			
			//Attach to the material node
			node->attachNode(newNode);
		}
		
		void OctreeNode::buildTree(	const vector<WorldVertex>& vertices,
								const vector<WorldTriangle>& polys, 
								const float nodeWidth, 
								const slVec3& centre) {
			
			m_Centre = centre;
			m_Width = width;
			
			AABB childBox[8];
			vector<WorldTriangle> childPolys[8];
			
			for (uint i = 0; i < 8; ++i) {
				createChildAABB(childBox[i], i, width, centre);
			}
			
			for (vector<WorldTriangle>::iterator it = polys.begin(); it != polys.end(); ++it) {
				//Go through the polys, if it fits into a child box then add it to the child list
				//if not assign it to this node
				slVec3 v1, v2, v3;
				bool childFound = false;
				
				v1 = vertices[(*it).m_Index[0]].m_Pos;
				v2 = vertices[(*it).m_Index[1]].m_Pos;
				v3 = vertices[(*it).m_Index[2]].m_Pos;
			
				for (uint i = 0; i < 8; ++i) {
					if (childBox[i].doesTriangleFit(v1, v2, v3)) {
						childFound = true;
						childPolys[i].push_back((*it));
					}
				}
				
				if (!childFound) {
					//We add to this node
					addTriangleToNode((*it), vertices);
				}
			}
			
			for (uint i = 0; i < 8; ++i) {
				buildTree(vertices, childPolys[i], childBox[i].getMaxWidth(), childBox[i].getCentre())
			}
			
		}
	}
}
